#include "tcpdevice.h"

#include "tools/m_mutex.h"

#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <poll.h>
#include <netdb.h>

#include <logger.h>

#define SOCKET_ERROR    (-1)
#define INVALID_SOCKET  SOCKET_ERROR
#include "iot_debug.h"
#include <errno.h>


class TcpDevicePrivate {
public:
    TcpDevicePrivate(const char *ip_addr, uint32_t port);


    bool set_ipv4_options(void);
    bool init(void);

    bool readable(uint32_t timeout);

    bool connected_;
    int socket_;
    MMutex mtx_;

    std::string ip_;
    uint32_t port_;
};

TcpDevicePrivate::TcpDevicePrivate(const char *ip_addr, uint32_t port) :
    ip_(ip_addr),
    port_(port)
{
    init();
}

bool TcpDevicePrivate::set_ipv4_options()
{
    int rc;
    int option;

    /* Set the TCP no delay flag */
    /* SOL_TCP = IPPROTO_TCP */
    option = 1;
    rc = ::setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY,
                      reinterpret_cast<const char *>(&option), sizeof(int));
    if (rc == -1) {
        return false;
    }

    /* If the OS does not offer SOCK_NONBLOCK, fall back to setting FIONBIO to
     * make sockets non-blocking */
    /* Do not care about the return value, this is optional */
#if !defined(SOCK_NONBLOCK) && defined(FIONBIO)
#ifdef WIN32
    {
        /* Setting FIONBIO expects an unsigned long according to MSDN */
        u_long loption = 1;
        ioctlsocket(socket_, FIONBIO, &loption);
    }
#else
    option = 1;
    ioctl(s, FIONBIO, &option);
#endif
#endif

#ifndef WIN32
    /**
     * Cygwin defines IPTOS_LOWDELAY but can't handle that flag so it's
     * necessary to workaround that problem.
     **/
    /* Set the IP low delay option */
    option = IPTOS_LOWDELAY;
    rc = setsockopt(socket_, IPPROTO_IP, IP_TOS,
                   (const void *)&option, sizeof(int));
    if (rc == -1) {
        return false;
    }
#endif

    return true;
}


bool TcpDevicePrivate::init()
{
    return true;
}

bool TcpDevicePrivate::readable(uint32_t timeout)
{
    fd_set rset;
    struct timeval tv;

    FD_ZERO(&rset);
    FD_SET(socket_, &rset);

    tv.tv_sec = timeout/1000;
    tv.tv_usec = (timeout-tv.tv_sec*1000)*1000;

    int s_rc;
    while ((s_rc=::select(socket_+1, &rset, NULL, NULL, &tv)) == -1) {
        if (errno == EINTR) {
            log_error("A non blocked signal was caught\n");
            /* Necessary after an error */
            FD_ZERO(&rset);
            FD_SET(socket_, &rset);
        } else {
            return false;
        }
    }

    if (s_rc == 0) {
        errno = ETIMEDOUT;
        return false;
    }

    return true;
}

TcpDevice::TcpDevice(const char *dev_name, const char *ip_addr, uint32_t port):
    private_(new TcpDevicePrivate(ip_addr, port))
{

}

TcpDevice::~TcpDevice()
{
    disconnect();
    delete private_;
}

bool TcpDevice::isActived() const
{
    return private_->connected_;
}

bool TcpDevice::connect(int timeout)
{
    if (private_->socket_ != INVALID_SOCKET) {
        disconnect();
    }
    struct sockaddr_in server_add;
    server_add.sin_family = AF_INET;
    server_add.sin_port = htons(private_->port_);
    inet_aton(private_->ip_.c_str(), &server_add.sin_addr);

    if( (private_->socket_ = ::socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET){
        log_error("Fail on Creating Socket\n");
        goto exitCode;
    }

    if( ::connect(private_->socket_, (struct sockaddr*) &server_add, sizeof(struct sockaddr_in)) != 0 ){
        log_error("Fail on Connecting\n");
        goto exitCode;
    }

    private_->connected_ = true;
exitCode:
    if (!private_->connected_)
        disconnect();
    return private_->connected_;
}

void TcpDevice::disconnect()
{
    if (private_->socket_) {
        if (private_->connected_) {
            shutdown(private_->socket_, SHUT_RDWR);
        }
        close(private_->socket_);
    }
    private_->socket_ = INVALID_SOCKET;
    private_->connected_ = false;
}

uint32_t TcpDevice::read(void *buf, uint32_t max_byte, uint32_t timeout)
{
    if (private_->socket_ == INVALID_SOCKET || !private_->readable(timeout))
        return 0;

    int n = ::recv(private_->socket_, reinterpret_cast<char *>(buf), static_cast<int>(max_byte), 0);
    if (n < 0)
        n = 0;
    return static_cast<uint32_t>(n);
}

uint32_t TcpDevice::write(const void *buf, uint32_t max_byte)
{
    if (private_->socket_ == INVALID_SOCKET)
        return 0;

    int n = ::send(private_->socket_, reinterpret_cast<const char *>(buf), static_cast<int>(max_byte), 0);
    if (n < 0)
        n = 0;
    return static_cast<uint32_t>(n);
}

void TcpDevice::clear()
{

}

void TcpDevice::lock()
{
    private_->mtx_.lock();
}

void TcpDevice::unlock()
{
    private_->mtx_.unlock();
}

bool TcpDevice::tryLock()
{
    return private_->mtx_.trylock();
}
